package jmine.tec.rpc.client;

import static jmine.tec.utils.io.http.HttpMethod.POST;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import jmine.tec.rpc.client.provider.ClientStubFactory;
import jmine.tec.rpc.common.RPCException;
import jmine.tec.rpc.common.request.RestRequest;
import jmine.tec.rpc.common.request.RestResponse;
import jmine.tec.rpc.common.serialization.Serializer;
import jmine.tec.utils.UnmodifiableTuple;
import jmine.tec.utils.collection.MultiMap;
import jmine.tec.utils.collection.impl.EmptyMultiMap;
import jmine.tec.utils.io.http.Connector;
import jmine.tec.utils.io.http.exception.HttpClientErrorException;
import jmine.tec.utils.io.http.exception.HttpException;
import jmine.tec.utils.io.http.exception.HttpServerErrorException;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Testa um Stub, proxy responsável por empacotar e enviar chamadas feitas.
 * 
 * @author lundberg
 */
@RunWith(JMock.class)
public class AClientStubShould {

    private static final String BASE_PATH = "https://www.maps.com.br/rest/";

    private final Mockery context = new JUnit4Mockery();

    private final Connector connector = this.context.mock(Connector.class);

    private final Serializer serializer = this.context.mock(Serializer.class);

    private final RestRequest serviceRequest = new RestRequest("reverse", new Class<?>[]{ String.class }, new Object[]{ "abc" });

    private final InputStream request = new ByteArrayInputStream(new byte[]{ 1, 2, 3 });

    private final InputStream response = new ByteArrayInputStream(new byte[]{ 3, 2, 1 });

    private final MultiMap<String, String> query = new EmptyMultiMap<String, String>();

    private ClientStubFactory factory;

    private MockRemoteService service;

    /**
     * Init
     */
    @Before
    public void init() {
        this.factory = ClientStubFactoryBuilder.aClientStubFactory().with(this.connector).with(this.serializer).with(BASE_PATH).build();
        this.service = this.factory.create(MockRemoteService.class, "mock");
    }

    /**
     * Tests the behavior of the client stub, that should serialize parameters, dispatch, deserialize the answer and return.
     * 
     * @throws Throwable Throwable
     */
    @Test
    public void dispatchCall() throws Throwable {
        this.context.checking(new Expectations() {
            {
                this.oneOf(AClientStubShould.this.serializer).serialize(AClientStubShould.this.serviceRequest);
                this.will(returnValue(AClientStubShould.this.request));

                this.oneOf(AClientStubShould.this.connector).makeRequestWithBody(BASE_PATH + "mock", POST, null,
                        AClientStubShould.this.query, AClientStubShould.this.request);
                this.will(returnValue(new UnmodifiableTuple<InputStream, MultiMap<String, Object>>(AClientStubShould.this.response, null)));

                this.oneOf(AClientStubShould.this.serializer).deserialize(AClientStubShould.this.response);
                this.will(returnValue(new RestResponse("cba")));
            }
        });
        assertThat(this.service.reverse("abc"), is(equalTo("cba")));
    }

    /**
     * Tests the behavior of the client stub, that should serialize parameters, dispatch, deserialize the answer and return.
     * 
     * @throws Throwable Throwable
     */
    @Test(expected = NullPointerException.class)
    public void rethrowRemoteException() throws Throwable {
        final NullPointerException e = new NullPointerException("Sample NPE.");
        this.context.checking(new Expectations() {
            {
                this.oneOf(AClientStubShould.this.serializer).serialize(AClientStubShould.this.serviceRequest);
                this.will(returnValue(AClientStubShould.this.request));

                this.oneOf(AClientStubShould.this.connector).makeRequestWithBody(BASE_PATH + "mock", POST, null,
                        AClientStubShould.this.query, AClientStubShould.this.request);
                this.will(returnValue(new UnmodifiableTuple<InputStream, MultiMap<String, Object>>(AClientStubShould.this.response, null)));

                this.oneOf(AClientStubShould.this.serializer).deserialize(AClientStubShould.this.response);
                this.will(returnValue(new RestResponse(e)));
            }
        });
        this.service.reverse("abc");
    }

    /**
     * Error handling
     * 
     * @throws HttpException HttpException
     * @throws IOException IOException
     */
    @Test(expected = RPCException.class)
    public void wrapHttpServerException() throws HttpException, IOException {
        this.context.checking(new Expectations() {
            {
                this.oneOf(AClientStubShould.this.serializer).serialize(AClientStubShould.this.serviceRequest);
                this.will(returnValue(AClientStubShould.this.request));

                this.oneOf(AClientStubShould.this.connector).makeRequestWithBody(BASE_PATH + "mock", POST, null,
                        AClientStubShould.this.query, AClientStubShould.this.request);
                this.will(throwException(new HttpServerErrorException(0, "Internal error.")));
            }
        });
        this.service.reverse("abc");
    }

    /**
     * Error handling
     * 
     * @throws HttpException HttpException
     * @throws IOException IOException
     */
    @Test(expected = RPCException.class)
    public void wrapHttpClientException() throws HttpException, IOException {
        this.context.checking(new Expectations() {
            {
                this.oneOf(AClientStubShould.this.serializer).serialize(AClientStubShould.this.serviceRequest);
                this.will(returnValue(AClientStubShould.this.request));

                this.oneOf(AClientStubShould.this.connector).makeRequestWithBody(BASE_PATH + "mock", POST, null,
                        AClientStubShould.this.query, AClientStubShould.this.request);
                this.will(throwException(new HttpClientErrorException(1, "Bad request.")));
            }
        });
        this.service.reverse("abc");
    }

    /**
     * Error handling
     * 
     * @throws HttpException HttpException
     * @throws IOException IOException
     */
    @Test(expected = RPCException.class)
    public void wrapIOException() throws HttpException, IOException {
        this.context.checking(new Expectations() {
            {
                this.oneOf(AClientStubShould.this.serializer).serialize(AClientStubShould.this.serviceRequest);
                this.will(returnValue(AClientStubShould.this.request));

                this.oneOf(AClientStubShould.this.connector).makeRequestWithBody(BASE_PATH + "mock", POST, null,
                        AClientStubShould.this.query, AClientStubShould.this.request);
                this.will(throwException(new IOException()));
            }
        });
        this.service.reverse("abc");
    }
}
